package com.bite.gateway.fiilter;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.bite.common.core.constants.CacheConstants;
import com.bite.common.core.constants.Constants;
import com.bite.common.core.constants.HttpConstants;
import com.bite.common.core.domain.LoginUser;
import com.bite.common.core.domain.R;
import com.bite.common.core.enums.ResultCode;
import com.bite.common.core.enums.UserIdentity;
import com.bite.common.core.utils.ThreadLocalUtil;
import com.bite.common.redis.service.RedisService;
import com.bite.common.core.utils.JwtUtils;
import com.bite.gateway.properties.IgnoreWhiteProperties;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.List;
@Slf4j
@Component
public class AuthFilter implements GlobalFilter,Ordered{
   // 排除过滤的 uri ⽩名单地址，在nacos⾃⾏添加
    @Autowired
    private IgnoreWhiteProperties ignoreWhite;
    @Value("${jwt.secret}")
    private String secret;
    @Autowired
    private RedisService redisService;
    //集成那两个接口，请求方法前先去拦截他，然后再去转发给具体的服务（先进入filter里面，完成一些事情)
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain
            chain) {
        //获取到http请求
        ServerHttpRequest request = exchange.getRequest();
        //得到url，得到请求的接口地址，登录接口是否需要对他进行身份认证。
        // 否（过滤掉登录接口) 引出一个白名单，白名单里面不需要验证身份
        String url = request.getURI().getPath();
// 跳过不需要验证的路径 ,ignoreWhite.getWhites(): 拿到nacos上配置的接口地址的白名单
        if (matches(url, ignoreWhite.getWhites())) { //判断接口是否在白名单里，假如在则不需要再去执行
            return chain.filter(exchange);
        }
//从http请求头中获取token ，走到这里接口不在白名单中，才是真正的执行身份认证逻辑
        //通过token进行身份认证，首先把token 获取出来
        String token = getToken(request);
        //判断token是否为空
        if (StrUtil.isEmpty(token)) {
            return unauthorizedResponse(exchange, "令牌不能为空");
        }
        Claims claims;
        try {
            //payload存着用户的敏感信息，比如唯一标识信息
            claims = JwtUtils.parseToken(token, secret); //获取令牌中信息 解析payload中信息
            if (claims == null) {
                //为什么不throw一个new异常,或者是那个全局处理异常Global那个
                //因为springcloud 是基于gateway 基于Webfix（但是我们的全局异常处理Global这个是
                //@RestCControllerAdvice基于SpringMVC的处理，不适用于Cloud
                return unauthorizedResponse(exchange, "令牌已过期或验证不正确！");
            }
        } catch (Exception e) {
            return unauthorizedResponse(exchange, "令牌已过期或验证不正确！");
        }
        String userKey = JwtUtils.getUserKey(claims); //获取jwt中的key
        //查询敏感数据是否还存在
        boolean isLogin = redisService.hasKey(getTokenKey(userKey));
        //从这里
        if (!isLogin) {
            return unauthorizedResponse(exchange, "登录状态已过期");
        }
        String userId = JwtUtils.getUserId(claims); //判断jwt中的信息是否完整
        if (StrUtil.isEmpty(userId)) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
        //到此，来看看token是否过期，因为只要redis过期，那么我就不用管理token了。
        //假如走到这里，说明携带了token,是正确，并且没有过期，所以应该判断redis当中存储的关于用户的信息是否正确
        LoginUser user = redisService.getCacheObject(getTokenKey(userKey),
                LoginUser.class);
        //判断当前请求是C端还是B端，C-client     B端-管理员能够登录
        if (url.contains(HttpConstants.SYSTEM_URL_PREFIX) &&
                !UserIdentity.ADMIN.getValue().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }
        if (url.contains(HttpConstants.FRIEND_URL_PREFIX) &&
                !UserIdentity.ORDINARY.getValue().equals(user.getIdentity())) {
            return unauthorizedResponse(exchange, "令牌验证失败");
        }

        //假如说延长token放到这里，是否你就可以在验证之后，转发之前完成延长token了,那么为什么不行呢？
        //但是假如添加之后耦合度就过高了，一件事变成了两件，假如我操作时候，可能就会互相影响，而且维护性不好
        //下面有一个过滤器的顺序值，假如我们根据一个顺序值，来搞俩过滤器是否可以呢？，也不是很好，假如还有安全的
        //是否还要加呢，但是我此时那个延长是不是还要放到这个新来的安全校验的后面。
        //并且指责上的问题，会把一些不符合要求的过滤掉，但是我们的token延长跟过滤没关系，所以就不是特别合适
        //那么我们该放到哪里呢？ 在拦截器中调用
        //每个线程都是一个副本，而且两个微服务正常假如再这里面放着ThreadLocal，就会进程隔离
        ThreadLocalUtil.set(Constants.USER_ID,userId);

        return chain.filter(exchange);
    }
    /**
     * 查找指定url是否匹配指定匹配规则链表中的任意⼀个字符串
     *
     * @param url 指定url
     * @param patternList 需要检查的匹配规则链表
     * @return 是否匹配
     */
    private boolean matches(String url, List<String> patternList) {
        if (StrUtil.isEmpty(url) || CollectionUtils.isEmpty(patternList)) {
            return false;
        }
        //接口地址如果和白名单其中一个匹配上，就返回true，如果便利完白名单中所有的地址都没有匹配的返回false.
        for (String pattern : patternList) {
            if (isMatch(pattern, url)) {
                return true;
            }
        }
        return false;
    }
    /**
     * 判断url是否与规则匹配
     * 匹配规则中：
     * pattern中可以写一些特殊字符
     * ? 表⽰单个字符;
     * * 表⽰⼀层路径内的任意字符串，不可跨层级;
     * ** 表⽰任意层路径;
     *
     * @param pattern 匹配规则
     * @param url 需要匹配的url
     * @return 是否匹配
     */
    private boolean isMatch(String pattern, String url) {
        AntPathMatcher matcher = new AntPathMatcher();
        return matcher.match(pattern, url);
    }
    /**
     * 获取缓存key
     */
    private String getTokenKey(String token) {
        return CacheConstants.LOGIN_TOKEN_KEY + token;
    }
    /**
     * 从请求头中获取请求token
     */
    private String getToken(ServerHttpRequest request) {
        String token =              //获取他是一个字段，专门来存放token的凭据的
                request.getHeaders().getFirst(HttpConstants.AUTHENTICATION);
// 如果前端设置了令牌前缀，则裁剪掉前缀（比如那个bear）
        if (StrUtil.isNotEmpty(token) &&
                token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return token;
    }

    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String
            msg) {
        //拼接，调用下面的方法
        log.error("[鉴权异常处理]请求路径:{}", exchange.getRequest().getPath());
        return webFluxResponseWriter(exchange.getResponse(), msg,
                ResultCode.FAILED_UNAUTHORIZED.getCode());
    }
    //拼装webflux模型响应
    private Mono<Void> webFluxResponseWriter(ServerHttpResponse response,
                                             String msg, int code) {
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE,
                MediaType.APPLICATION_JSON_VALUE);
        R<?> result = R.fail(code, msg);
        DataBuffer dataBuffer =
                response.bufferFactory().wrap(JSON.toJSONString(result).getBytes());
        return response.writeWith(Mono.just(dataBuffer));

    }
    //得到一个顺序，过滤器的执行，比如你先完成身份认证，相当于很多道关卡，值越小，执行排序越靠前
    //如第一第二第三，且正负敏感-200和1 负数小
    @Override
    public int getOrder() {
        return -200;
    }
    public static void main(String[] args) {
        AuthFilter authFilter = new AuthFilter();
// 测试 ?
        String pattern = "/sys/?bc";
        System.out.println(authFilter.isMatch(pattern,"/sys/abc"));
        System.out.println(authFilter.isMatch(pattern,"/sys/cbc"));
        System.out.println(authFilter.isMatch(pattern,"/sys/acbc"));
        System.out.println(authFilter.isMatch(pattern,"/sdsa/abc"));
        System.out.println(authFilter.isMatch(pattern,"/sys/abcw"));
// 测试*
// String pattern = "/sys/*/bc";
// System.out.println(authFilter.isMatch(pattern,"/sys/a/bc"));
//
        System.out.println(authFilter.isMatch(pattern,"/sys/sdasdsadsad/bc"));
// System.out.println(authFilter.isMatch(pattern,"/sys/a/b/bc"));
// System.out.println(authFilter.isMatch(pattern,"/a/b/bc"));
// System.out.println(authFilter.isMatch(pattern,"/sys/a/b/"));
// 测试**
// String pattern = "/sys/**/bc";
// System.out.println(authFilter.isMatch(pattern, "/sys/a/bc"));

// System.out.println(authFilter.isMatch(pattern,
  //      "/sys/sdasdsadsad/bc"));
// System.out.println(authFilter.isMatch(pattern, "/sys/a/b/bc"));
// System.out.println(authFilter.isMatch(pattern,
   //     "/sys/a/b/s/23/432/fdsf///bc"));
// System.out.println(authFilter.isMatch(pattern,
   //     "/a/b/s/23/432/fdsf///bc"));
// System.out.println(authFilter.isMatch(pattern,
    //    "/sys/a/b/s/23/432/fdsf///"));
    }
}
